package service

import (
	"encoding/hex"
	"github.com/sirupsen/logrus"
	"log"
	"math"
	"math/big"
	"morleyStoneHome/config"
	"morleyStoneHome/dao"
	"morleyStoneHome/model"
	"morleyStoneHome/util"
	"time"
)

type stoService struct{}

var StoService = newStoService()

var SysPool []*model.SysPool

func newStoService() *stoService {
	return &stoService{}
}

//处理每日新人各自新增的质押（每日结算）
func (c *stoService) AddStake() {
	//拿到catcoin中pledged值为0的所有cat
	noPledgedList, err := dao.CatcoinDao.FindOneByPledged(0)
	if nil != err {
		logrus.Error(err) //todo:报错后应重试
	}
	for i := 0; i < len(noPledgedList); i++ {
		catcoin := noPledgedList[i]
		//根据这些cat，分别到pledge表中查最早的第一笔质押
		pledge, err := dao.PledgeDao.FindOne(catcoin.Addr)
		if nil != err {
			logrus.Error(err)
		}
		if len(pledge) == 0 {
			continue
		}
		//插入stoReward表//bug:如果没质押，但有推荐，表中会已有数据，这里再插入一条就重复了，应增加去重判断。
		stake, err := util.StringToFloat64(pledge[0].Amount)
		if nil != err {
			logrus.Error(err)
		}
		stoReward, err := dao.StoRewardDao.FindOneByAddr(pledge[0].Account)
		if nil != err {
			logrus.Error(err)
		}
		if len(stoReward) != 0 {
			//说明表中已存在，该条数据对应的地址是先推荐，有了推荐奖励，后质押的。
			m := make(map[string]interface{})
			m["pid"] = pledge[0].Pid
			m["staking"] = pledge[0].Amount
			m["stake_time"] = pledge[0].LedgerTime
			m["stake_life"] = pledgeLife(pledge[0])
			dao.StoRewardDao.UpdateByMap(catcoin.Uid, m)
		} else {
			dao.StoRewardDao.Insert(&model.StoReward{
				catcoin.Uid,
				catcoin.Addr,
				stake, //string转float64
				0,
				0,
				0,
				0,
				pledge[0].LedgerTime,
				pledgeLife(pledge[0]),
				pledge[0].Pid,
				0,
			})
		}

		//更新catcoin表pledged值为1
		catcoin.Pledged = 1
		dao.CatcoinDao.Update(catcoin.Addr, &catcoin)
	}
}

func pledgeLife(p *model.Pledge) int {
	now := time.Now()
	sub := now.Sub(p.LedgerTime).Hours()
	floor := math.Floor(sub / 24)
	if len(SysPool) == 0 {
		var err error
		SysPool, err = dao.ChainDao.FindPoolList()
		if nil != err {
			logrus.Error("service.pledgeLife.FindPoolList :: failed :", err)
		}
	}
	for i := 0; i < len(SysPool); i++ {
		pool := SysPool[i]
		if pool.Pid == p.Pid {
			return pool.Life - int(floor)
		}
	}
	return -1
}

//计算每日总质押利息（非推广收益），录入数据库
func (c *stoService) TotalSreward() {
	var ts float64
	all, err := dao.PledgeDao.FindAll()
	if nil != err {
		logrus.Error(err) //todo:报错后应重试
	}
	for i := 0; i < len(all); i++ {
		pledge := all[i]
		if isAlive(pledge) {
			temp, err := util.StringToFloat64(pledge.Amount)
			pid := pledge.Pid
			hisSreward := getSreward(temp, pid, SysPool)
			if nil != err {
				logrus.Error(err)
			}
			ts = ts + hisSreward
		}
	}
	err = dao.ChainDao.InsertSreward(&model.StoSreward{
		time.Now(),
		ts,
	})
	if nil != err {
		logrus.Error(err.Error())
	}
}

func getSreward(temp float64, pid int, pool []*model.SysPool) float64 {
	var res float64
	for i := 0; i < len(pool); i++ {
		if pool[i].Pid == pid {
			life := pool[i].Life
			rate := pool[i].Rate
			res = temp * rate / float64(life)
		}
	}
	return res
}

//校验一笔质押是否到期
func isAlive(pledge *model.Pledge) bool {
	return pledgeLife(pledge) > 0
}

//处理每日更新奖励（每日结算，按市场、团队质押量及总释放量计算出）（需要查询catcoin表）
func (c *stoService) HandleReward() {
	//totalRelease是白皮书规定的，手工录入数据库，从表中拿
	totalRelease1, err := dao.ChainDao.FindRelease()
	if nil != err {
		logrus.Error("service.HandleReward :: get totalRelease error :", err)
	}
	if len(totalRelease1) == 0 {
		logrus.Info("service.HandleReward :: get totalRelease failed : no data in database")
	}
	totalRelease := totalRelease1[0].ReleasePd
	//totalStakeReward 每天会计算一次放到数据库，从表中拿
	totalStakeReward1, err := dao.ChainDao.GetSreward()
	var totalStakeReward float64
	if totalStakeReward1 != nil {
		totalStakeReward = totalStakeReward1.Sreward
		logrus.Info("service.HandleReward::totalStakeReward getted ====> ", totalStakeReward)
	} else {
		logrus.Error("service.HandleReward :: cannot get total stake reward : ", err.Error())
	}
	//市场上所有被邀请人的总质押量
	sum := sumFollowStake()
	logrus.Info("service.HandleReward :: total Release: ", totalRelease, "; total stake reward :", totalStakeReward, "; total follow stake: ", sum)
	//所有的推广者：list
	list, err := dao.CatcoinDao.FindAllMarketerList()
	if nil != err {
		logrus.Error(err)
	}
	for i := 0; i < len(list); i++ {
		uid := list[i].Uid //list[i]是其中一个推广者,在catcoin表里查到的
		addr := list[i].Addr
		//从sto_reward表里查marketer，由于推广者可能不自己质押，所以表中可能是空的，有则查，无则插入一条。
		marketer := findOrInsertMarketer(uid, addr)
		//followStake是此marketer的所有直推的质押量，即团队业绩（只计算一代）。
		var followStake float64
		followerList, err := dao.CatcoinDao.FindFollowerList(uid)
		if nil != err {
			logrus.Error(err)
		}
		for j := 0; j < len(followerList); j++ {
			follower := followerList[j]
			one, err := dao.StoRewardDao.FindOneAlive(follower.Uid)
			if nil != err {
				logrus.Error(err)
			}
			if len(one) != 0 {
				followStake = followStake + one[0].Staking
			}
		}
		adReward := (totalRelease - totalStakeReward) * followStake / sum
		logrus.Info("stoService :: HandleReward() :: marketer ", addr, " get ad reward == > ", adReward)
		reward := newStoReward(&marketer[0], adReward)
		err = dao.StoRewardDao.Update(uid, reward)
		if nil != err {
			logrus.Error(err)
		}
	}
}

func findOrInsertMarketer(uid int, addr string) (marketer []model.StoReward) {
	marketer, err := dao.StoRewardDao.FindOne(uid) //从reward表里查一个
	if nil != err {
		logrus.Error(err)
	}
	if len(marketer) == 0 {
		var sr = &model.StoReward{
			uid,
			addr,
			0,
			0,
			0,
			0,
			0,
			time.Now(),
			0,
			-1,
			0,
		}
		err = dao.StoRewardDao.Insert(sr)
		marketer, err = dao.StoRewardDao.FindOne(uid)
		if nil != err {
			logrus.Error(err)
		}
	}
	return
}

//计算市场上所有被推荐人的总质押
func sumFollowStake() float64 {
	var sum float64
	list, err := dao.CatcoinDao.FindAllFollowerList()
	if nil != err {
		logrus.Error(err)
	}
	for i := 0; i < len(list); i++ {
		follower := list[i]
		one, err := dao.StoRewardDao.FindOneAlive(follower.Uid)
		if nil != err {
			logrus.Error(err)
		}
		if len(one) != 0 {
			sum = sum + one[0].Staking
		}
	}
	return sum

}

//生成更新reward对象
func newStoReward(sto *model.StoReward, adReward float64) *model.StoReward {
	return &model.StoReward{
		sto.Uid,
		sto.Addr,
		sto.Staking,
		sto.AdReward + adReward,
		sto.Withdraw,
		sto.Balance + adReward,
		sto.Freeze,
		sto.StakeTime,
		sto.StakeLife,
		sto.Pid,
		sto.Nonce,
	}
}

//每天给reward表的质押余生减一
func (c *stoService) UpdateLife() {
	err := dao.StoRewardDao.UpdateLife()
	if nil != err {
		logrus.Error(err)
	} else {
		logrus.Info("service.UpdateLife :: life-1 success")
	}
}

//=======================上面是来钱的，下面是出钱的================================
//查询单条记录
func (c *stoService) FindAmount(addr string) (amount *big.Int, balance float64, uid int, user *model.StoReward) {
	one, err := dao.CatcoinDao.FindOne(addr)
	var stoReward []model.StoReward
	if nil != err {
		logrus.Error(err)
	} else if len(one) != 0 {
		stoReward, err = dao.StoRewardDao.FindOne(one[0].Uid)
		if nil != err {
			logrus.Error(err)
		}
	} else {
		return big.NewInt(0), 0, 0, nil
	}
	//处理还没有奖励的情况
	if len(stoReward) == 0 {
		return big.NewInt(0), 0, one[0].Uid, nil
	}
	user = &stoReward[0]
	amount = util.FloatToBigInt(user.Balance)
	//返回amount和balance的用意是不同数据类型，有不同用途。
	return amount, user.Balance, user.Uid, user
}

//签名
func (*stoService) SignData(addr string) (sig map[string]interface{}, msg string, err error) {
	sig = make(map[string]interface{})
	amount, b, _, reward := StoService.FindAmount(addr) //查找准备提现的余额，余额有多少，就让他提多少
	//如果余额为0，直接返回。
	if b == 0 {
		return nil, "可提余额为0", nil
	}
	//增加逻辑：如果上一笔提现未到账，不允许再次提现。
	if reward.Freeze != 0 {
		return nil, "请等待区块确认上次提现", nil
	}
	//nonce, err := strconv.Atoi(nonceStr) //string转int
	nonce := reward.Nonce
	if nil != err {
		log.Panicf(err.Error())
		msg = "nonce值转换错误"
		return nil, msg, err
	}
	logrus.Info("service.SignData :: addr = ", addr, "; nonce = ", nonce, "; amount = ", amount)
	data := NewTypedData(addr, nonce, amount)    //生成待签名的TypedData对象
	var pkey = config.Instance.MyPrivateKey      //配置文件取出私钥，是字符串
	realPrivKey := StringToPriKey(pkey)          //字符串转真正的私钥对象
	signer := NewDefaultSigner(realPrivKey)      //传入私钥，生成签名器
	byteArray, err := signer.SignTypedData(data) //签名
	if nil != err {
		logrus.Error(err)
		msg = "签名失败"
		return nil, msg, err
	}
	signedData := hex.EncodeToString(byteArray)                                           //转换成16进制字符串
	logrus.Info(time.Now().String() + "service.SignData :: signedData ==> " + signedData) //日志记录签名数据
	sig["signedData"] = signedData
	sig["TypedDataMessage"] = data.Message
	//sig["data"] = data//生产环境应注释data
	return sig, "", nil
}

func (c *stoService) Freeze(addr string) error {
	_, b, uid, reward := StoService.FindAmount(addr)
	err := freezeBalance(reward) //给出结果之前把余额冻结
	if nil != err {
		logrus.Error(err)
		return err
	}
	err = recordSign(uid, addr, reward.Nonce, b)
	if nil != err {
		logrus.Error(err)
		return err
	}
	logrus.Info("service.Freeze :: uid = ", uid, " ; addr = ", addr, " ; nonce = ", reward.Nonce, " ; balanceToFreeze = ", b)
	return nil
}

//冻结余额
func freezeBalance(sto *model.StoReward) error {
	data := make(map[string]interface{})
	data["balance"] = 0                       //零值字段
	data["freeze"] = sto.Balance + sto.Freeze //todo:精度损失处理
	err := dao.StoRewardDao.UpdateByMap(sto.Uid, data)
	if nil != err {
		logrus.Error(err)
		return err
	}
	return nil
}

// 记录签名快照
func recordSign(uid int, addr string, nonce int, freeze float64) error {
	stoWithdraw := &model.StoWithdraw{
		uid,
		addr,
		freeze,
		time.Now(),
		nonce,
		1,
		"",
	}
	err := dao.StoWithdrawDao.Insert(stoWithdraw)
	return err
}

func (c *stoService) HandleDeleteFreeze() error {
	list, err2 := dao.StoWithdrawDao.FindFreezeList()
	if nil != err2 {
		logrus.Error(err2)
	}
	for i := 0; i < len(list); i++ {
		stoWithdraw := list[i]
		tx, err2 := dao.ChainDao.GetTx(stoWithdraw.Addr, stoWithdraw.Nonce)
		if nil != err2 {
			logrus.Error(err2)
		}
		if tx == nil {
			continue
		} else if tx.TxState == 100 {
			err2 = c.DeleteFreeze(tx.TAccount, tx.Nonce, tx.Hash)
			if nil != err2 {
				return err2
			}
		} else if tx.TxState == -1 {
			err2 = c.GiveBack(stoWithdraw.Uid, tx.Nonce, tx.Hash)
			if nil != err2 {
				return err2
			}
		} else {
			continue
		}
	}
	return nil
}

func (c *stoService) DeleteFreeze(addr string, nonce int, hash string) error {

	//扫快确认到账之后删除冻结余额
	byAddr, err := dao.StoRewardDao.FindOneByAddr(addr)
	if nil != err {
		logrus.Error(err)
		return err
	}
	sto := &byAddr[0]
	////如果链上拉取的提现数量与冻结量不符，则不进行下一步。//todo:是否有必要校验？过程中可能有精度丢失
	//withdrawBigint := util.FloatToBigInt(withdraw)
	//freezeBigInt := util.FloatToBigInt(sto.Freeze)
	//if 0 != withdrawBigint.Cmp(freezeBigInt) {
	//	logrus.Error("链上拉取的提现数量与冻结量不符")
	//	return util.NewError(1, "链上拉取的提现数量与冻结量不符")
	//}
	data := make(map[string]interface{})
	//data["balance"] = byAddr[0].Balance
	data["freeze"] = 0
	data["nonce"] = nonce + 1
	err = dao.StoRewardDao.UpdateByMap(sto.Uid, data)
	if nil != err {
		logrus.Error(err)
		return err
	}
	data2 := make(map[string]interface{})
	data2["status"] = 2
	data2["hash"] = hash
	err = dao.StoWithdrawDao.UpdateByMap(sto.Uid, data2)
	if nil != err {
		logrus.Error(err)
		return err
	}
	logrus.Info("service.DeleteFreeze :: addr = ", addr, " ; nonceAfterConfirm = ", nonce+1)
	return nil
}

func (c *stoService) GiveBack(uid int, nonce int, hash string) error {
	sto, err := dao.StoRewardDao.FindOne(uid)
	if nil != err || len(sto) == 0 {
		logrus.Error(err, "||len(sto)==0")
		return err
	}
	data := make(map[string]interface{})
	data["balance"] = sto[0].Balance + sto[0].Freeze
	data["freeze"] = 0
	data["nonce"] = nonce + 1
	err = dao.StoRewardDao.UpdateByMap(uid, data)
	if nil != err {
		logrus.Error(err)
		return err
	}
	data2 := make(map[string]interface{})
	data2["status"] = 3
	data2["hash"] = hash
	err = dao.StoWithdrawDao.UpdateByMap(sto[0].Uid, data2)
	if nil != err {
		logrus.Error(err)
		return err
	}
	logrus.Info("service.GiveBackFreeze :: addr = ", sto[0].Addr, " ; balance before = ", sto[0].Balance, " ; balance after = ", sto[0].Freeze)
	return nil
}

//校验不用做了
//func (c *stoService) Verify(addr string) (string, error) {
//	//先校验地址是否在wallet_device_uid表里存在
//	one, err := dao.WalletDeviceUidDao.FindOne(addr)
//	if err != nil {
//		logrus.Error(err)
//		return "地址校验失败", err
//	}
//	if len(one) == 0 {
//		return "该地址不存在或不是ERC20", util.NewError(1, "该地址不存在或不是ERC20")
//	}
//	return "地址校验通过", nil
//}
